use crate::utils::{
    dom::{get_element_by_id, get_win_inner_width},
    helper::get_document,
};
//use gloo::console::log;
use lazy_static::lazy_static;
use regex::Regex;
use wasm_bindgen::JsCast;
use web_sys::{
    Element, HtmlButtonElement, HtmlDivElement, HtmlElement, HtmlInputElement, Node, NodeList,
    Selection,
};

pub struct EditorApi {}
impl EditorApi {
    pub const SEARCH_TEXT_ID: &str = "search_text";
    pub const EDITOR_ID: &str = "editor-x-1";
    pub const CONTAINER_ID: &str = "editor-container";
    const DEFAULT_NOTE_ID: u64 = 0;
    const DEFAULT_NOTE_VERSION: u64 = 0;

    pub fn editor_title_id() -> String {
        format!("{}_title_id", Self::EDITOR_ID)
    }

    pub fn set_editor_title(title: Option<&str>) {
        if let Some(title1) = title {
            let title_element = get_element_by_id(&Self::editor_title_id())
                .expect("set_editor_title: get editor_title error");
            title_element.set_inner_html(title1);
        }
    }
    pub fn editor_title() -> Option<String> {
        if let Some(first_node) = EditorApi::editor_node().child_nodes().first_child() {
            first_node.text_content()
        } else {
            None
        }
    }

    pub fn set_note_version(version: u64) {
        let title_input = get_element_by_id(&Self::get_note_version_input_id())
            .expect("get note_version_input error")
            .dyn_into::<HtmlInputElement>()
            .expect("dyn_into HtmlInputElement error");
        title_input.set_value(&version.to_string());
    }
    pub fn init_note_version() {
        Self::set_note_version(Self::DEFAULT_NOTE_VERSION);
    }

    pub fn set_note_id(note_id: u64) {
        let title_input = get_element_by_id(&Self::get_note_id_input_id())
            .expect("get note_id_input error")
            .dyn_into::<HtmlInputElement>()
            .expect("dyn_into HtmlInputElement error");
        title_input.set_value(&note_id.to_string());
    }
    pub fn init_note_id() {
        Self::set_note_id(Self::DEFAULT_NOTE_ID);
    }
    pub fn get_note_id() -> u64 {
        let id = get_element_by_id(&Self::get_note_id_input_id())
            .expect("get_note_id:get note_id_input error")
            .dyn_into::<HtmlInputElement>()
            .expect("get_note_id: dyn_into HtmlInputElement error")
            .value();

        if id.is_empty() {
            0
        } else {
            id.parse::<u64>().expect("get_note_id:parse u64 error")
        }
    }
    pub fn get_note_version() -> u64 {
        let version_value = get_element_by_id(&Self::get_note_version_input_id())
            .expect("get_note_version: get note_version_input error")
            .dyn_into::<HtmlInputElement>()
            .expect("get_note_version: dyn_into HtmlInputElement error")
            .value();
        if version_value.is_empty() {
            1
        } else {
            version_value
                .parse::<u64>()
                .expect("get_note_version: parse u64 error")
        }
    }
    pub fn get_search_box_element() -> Element {
        get_element_by_id(Self::SEARCH_TEXT_ID).expect("get search_box error")
    }

    pub fn get_editor_element() -> Element {
        get_element_by_id(Self::EDITOR_ID).expect("get editor error")
    }

    pub fn editor_node() -> Node {
        Self::get_editor_element()
            .dyn_into::<Node>()
            .expect("editor_node error")
    }

    pub fn get_note_id_input_id() -> String {
        format!("{}_note_id", Self::CONTAINER_ID)
    }
    pub fn get_note_version_input_id() -> String {
        format!("{}_note_version", Self::CONTAINER_ID)
    }
    pub fn set_note_content(content: &str) {
        let editor_ref = Self::get_editor_element()
            .dyn_into::<HtmlDivElement>()
            .expect("set_note_content: dyn_into HtmlDivElement error");

        editor_ref.set_inner_html(content);
    }
    pub fn get_add_button_id() -> String {
        format!("{}_add_button", Self::CONTAINER_ID)
    }
    pub fn get_remove_button_id() -> String {
        format!("{}_remove_button", Self::CONTAINER_ID)
    }
    pub fn get_add_button() -> HtmlButtonElement {
        get_element_by_id(&Self::get_add_button_id())
            .expect("get_add_button error")
            .dyn_into::<HtmlButtonElement>()
            .expect("dyn_into HtmlButtonElement error")
    }
    pub fn get_editor_container_element() -> HtmlDivElement {
        get_element_by_id(Self::CONTAINER_ID)
            .expect("get container element error")
            .dyn_into::<HtmlDivElement>()
            .expect("dyn_into HtmlDivElement error")
    }
    pub fn show_editor_container() {
        let avail_width = get_win_inner_width().expect("get_win_inner_height error");

        let add_button = Self::get_add_button();
        let _ = add_button.style().set_property(
            "transform",
            &format!("translate({}px, 0px)", avail_width * 2.0),
        );

        let div = Self::get_editor_container_element();
        let _ = div.style().set_property("transform", "translate(0px, 0px)");
    }
    pub fn clear_note() {
        let editor_ref = Self::get_editor_element();
        let document = get_document();

        let new_element = document
            .create_element("div")
            .expect("clear_note: create_element error");
        new_element.set_inner_html("<br/>");

        editor_ref.replace_children_with_node_1(
            &new_element
                .dyn_into::<Node>()
                .expect("clear_note: dyn_into Node error"),
        );
    }
    pub fn get_note_content() -> String {
        Self::get_editor_element().inner_html()
    }
    pub fn hide_editor_container() {
        let div = Self::get_editor_container_element();
        let avail_width = get_win_inner_width().expect("get_win_inner_height error");

        let add_button = Self::get_add_button();
        let _ = add_button
            .style()
            .set_property("transform", &format!("translate({}px, 0px)", avail_width));

        let _ = div
            .style()
            .set_property("transform", &format!("translate(-{}px, 0px)", avail_width));
    }

    pub fn is_text_node(node: Option<&Node>) -> bool {
        if let Some(n) = node {
            n.node_type() == 3
        } else {
            false
        }
    }
    pub fn is_top_level_node(node: Option<&Node>) -> bool {
        Self::editor_node()
            .child_nodes()
            .find_root_node(node)
            .is_some()
    }
    pub fn get_focus_node() -> Option<Node> {
        Self::get_selection()?.focus_node()
    }
    pub fn get_selection() -> Option<Selection> {
        get_document().get_selection().expect("get_selection error")
    }

    pub fn new_line_element() -> HtmlDivElement {
        let document = get_document();
        let new_element = document
            .create_element("div")
            .expect("append_line_and_focus: create_element error")
            .dyn_into::<HtmlDivElement>()
            .expect("append_line_and_focus: dyn_into HtmlElement error");
        new_element.set_inner_html("&nbsp;");
        new_element
    }
}

pub trait NodeListHelper {
    fn find_root_node(&self, node: Option<&Node>) -> Option<Node>;
    fn first_child(&self) -> Option<Node>;
}

impl NodeListHelper for NodeList {
    fn find_root_node(&self, node: Option<&Node>) -> Option<Node> {
        if node.is_none() {
            return None;
        }
        for index in 0..self.length() {
            let current_node = self.item(index)?;
            if current_node.contains(node) {
                return Some(current_node);
            }
        }
        None
    }
    fn first_child(&self) -> Option<Node> {
        self.item(0)
    }
}

pub trait ElementHelper {
    fn insert_after<'a>(&self, new_element: &'a HtmlDivElement) -> &'a HtmlDivElement;
    fn new_focus(&self);
}

impl ElementHelper for HtmlDivElement {
    fn insert_after<'a>(&self, new_element: &'a HtmlDivElement) -> &'a HtmlDivElement {
        let _ = self.insert_adjacent_element("afterend", new_element);
        new_element
    }
    fn new_focus(&self) {
        if let Some(selection) = EditorApi::get_selection() {
            let _ = selection.remove_all_ranges();

            let _ = selection.select_all_children(self);
        }
        let _ = self.focus();
    }
}

pub struct OutlineApi {}

impl OutlineApi {
    const OUTLINE_TYPE: &str = "outline_type";
    const OUTLINE_TYPES_LEN: usize = 4;
    const OUTLINE_TYPES: [&str; Self::OUTLINE_TYPES_LEN] = ["t1", "t2", "t3", "t4"];
    const OUTLINE_TYPE_CSS: [&str; Self::OUTLINE_TYPES_LEN] = [
        "font-size:26px;font-weight:bold",
        "font-size:22px;font-weight:bold",
        "font-size:18px;font-weight:bold",
        "font-size:14px;font-weight:bold",
    ];
    pub fn is_outline(node: Option<&Node>) -> bool {
        if let Some(n) = node {
            let outline_type = n
                .clone()
                .dyn_into::<Element>()
                .expect("is_outline: dyn_into Element error")
                .get_attribute(Self::OUTLINE_TYPE);
            outline_type.is_some()
        } else {
            false
        }
    }
    pub fn is_outline_tag(text: &str) -> bool {
        lazy_static! {
            static ref OUTLINE_REG: Regex = Regex::new(r"t[1-5]:").unwrap();
        }
        OUTLINE_REG.is_match(text)
    }
    fn get_outline_name(text: &str) -> String {
        text[0..text.trim().len() - 1].to_string()
    }
    fn get_outline_css(name: &str) -> String {
        if let Some(index) = Self::OUTLINE_TYPES.iter().position(|&x| x == name) {
            Self::OUTLINE_TYPE_CSS[index].to_string()
        } else {
            Self::OUTLINE_TYPE_CSS[Self::OUTLINE_TYPES_LEN - 1].to_string()
        }
    }
    pub fn build_outline(node: Option<&Node>, tag: &str) {
        if !Self::is_outline_tag(tag) {
            return;
        }
        if let Some(nd) = node {
            let document = get_document();
            let new_element = document
                .create_element("div")
                .expect("build_outline: create_element error")
                .dyn_into::<HtmlElement>()
                .expect("build_outline: dyn_into HtmlElement error");

            let outline_tag_name = &Self::get_outline_name(tag);
            let _ = new_element.set_attribute(Self::OUTLINE_TYPE, outline_tag_name);
            new_element.set_inner_html("&nbsp;");
            new_element
                .style()
                .set_css_text(&Self::get_outline_css(outline_tag_name));

            nd.clone()
                .dyn_into::<Element>()
                .expect("build_outline: dyn_into Element error")
                .replace_children_with_node_1(
                    &new_element
                        .clone()
                        .dyn_into::<Node>()
                        .expect("clear_note: dyn_into Node error"),
                );
            let _ = new_element.focus();
        }
    }
}
